#include "RandGen.h"
#include "LIData.h"
#include "LIOperator.inl"
#include "LargeIntegerMeta.h"
#include <time.h>
#include <vector>
#include <string>
#include <memory>
#include "timer.h"

//Debug use.
#include <iostream>

template<typename T>
struct UnitTest : public T {

	using typename T::TSP_IN;
	using typename T::TSP_OUT;
	using typename T::REF_IN;
	using typename T::REF_OUT;

	//using std::tr1::shared_ptr;

	UnitTest(const int pTestSize) : m_TestSize(pTestSize){}

	void operator()(){
		simple_rand randGen(time(NULL));
		
		vector<TSP_IN>  t_in0(m_TestSize),t_in1(m_TestSize);
		vector<TSP_OUT> t_out(m_TestSize);

		vector<REF_IN>  r_in0(m_TestSize),r_in1(m_TestSize);
		vector<REF_OUT> r_out(m_TestSize);

		// data init
		for (int i=0; i<m_TestSize; i++)
		{
			this->init_refData(r_in0[i],randGen);
			this->init_refData(r_in1[i],randGen);

			this->init_tstData(t_in0[i],r_in0[i]);
			this->init_tstData(t_in1[i],r_in1[i]);
		}

		// timer
		PROFILE::CStopWatch timer;

		timer.startTimer();
		// run test
		for (int i=0; i<m_TestSize; i++)
		{
			this->run_test(t_in0[i],t_in1[i],t_out[i]);
		}
		timer.stopTimer();
		double test_time = timer.getElapsedTime();

		timer.startTimer();
		// run reference
		for (int i=0; i<m_TestSize; i++)
		{
			this->run_ref(r_in0[i],r_in1[i],r_out[i]);
		}
		timer.stopTimer();
		double refer_time = timer.getElapsedTime();

		// compare result
		int idx = 0;
		for (; idx<m_TestSize; idx++)
		{
			bool pass = this->comp_result(t_out[idx],r_out[idx]);
			if (!pass)
			{

				std::ofstream of("log.txt");
				of.fill('0');
				of<<"input 0: "<<t_in0[idx]<<std::endl;
				of<<"input 1: "<<t_in1[idx]<<std::endl;
				of<<"sse mul: "<<t_out[idx]<<std::endl;
				of<<"ref mul: "<<r_out[idx]<<std::endl;
				of.close();

				std::cout<<T::Error_String<<std::endl;
				break;
			}
		}
		if (m_TestSize == idx)
		{
			std::cout<<T::Pass_String<<std::endl;
		}
		std::cout<<"Testing operation takes "<<test_time<<" seconds."<<std::endl;
		std::cout<<"Reference operation takes "<<refer_time<<" seconds."<<std::endl;
	}

private:
	const int m_TestSize;
};

template<typename T, int N>
struct Test_LInt_LargeIntegerMeta {
	typedef LARGE_INT::LargeInt<T,N> TSP_IN;
	typedef LargeIntegerMeta<T,N>	 REF_IN;
	typedef LARGE_INT::LargeInt<T,2*N> TSP_IN_2;
	typedef LargeIntegerMeta<T,2*N>	 REF_IN_2;

	void init_refData(REF_IN &pRef, simple_rand &pRandGen){
		
		const int size = REF_IN::ELEMT_SIZE;
		T *databuffer = new T[size];
		for (int i=0;i<size;i++)
		{
			databuffer[i] = T(pRandGen());
		}
		
		pRef = REF_IN(databuffer);

		delete[] databuffer;
	}

	void init_tstData(TSP_IN &pTst, const REF_IN &pRef){
		pTst = TSP_IN(pRef.m_data);
	}

};

template<typename T>
struct Test_add : public T {

	using typename T::TSP_IN;
	using typename T::REF_IN;

	typedef TSP_IN TSP_OUT;
	typedef REF_IN REF_OUT;

	std::string Error_String;
	std::string Pass_String;

	Test_add():Error_String("ADD Test Fail"),Pass_String("ADD Test Pass"){}

	void run_test(const TSP_IN& pT0, TSP_IN& pT1, TSP_OUT &pOT)
	{
		pOT = pT0;

		typename TSP_IN::ADP_TYP adp0(pOT);
		typename TSP_IN::const_ADP_TYP adp1(pT1);

		LARGE_INT::OPERA_UINT32::opera_add_assign(adp0,adp1);
	}

	void run_ref(const REF_IN& pR0, const REF_IN& pR1, REF_OUT &pOT)
	{
		pOT = pR0 + pR1;
	}

	bool comp_result(TSP_OUT& pTst, const REF_OUT& pRef)
	{
		typename TSP_IN::const_ADP_TYP adp(pTst);
		const int size = TSP_OUT::SIZE;
		bool ret = true;
		for (int i=0;i<size;i++)
		{
			if (adp[i]!=pRef.m_data[i])
			{
				ret = false;
				break;
			}
		}
		return ret;
	}
};

template<typename T>
struct Test_sub : public T {

	using typename T::TSP_IN;
	using typename T::REF_IN;

	typedef TSP_IN TSP_OUT;
	typedef REF_IN REF_OUT;

	std::string Error_String;
	std::string Pass_String;

	Test_sub():Error_String("SUB Test Fail"),Pass_String("SUB Test Pass"){}

	void run_test(const TSP_IN& pT0, TSP_IN& pT1, TSP_OUT &pOT)
	{
		pOT = pT0;

		typename TSP_IN::ADP_TYP adp0(pOT);
		typename TSP_IN::const_ADP_TYP adp1(pT1);

		LARGE_INT::OPERA_UINT32::opera_sub_assign(adp0,adp1);
	}

	void run_ref(const REF_IN& pR0, const REF_IN& pR1, REF_OUT &pOT)
	{
		pOT = pR0 - pR1;
	}

	bool comp_result(TSP_OUT& pTst, const REF_OUT& pRef)
	{
		typename TSP_IN::const_ADP_TYP adp(pTst);
		const int size = TSP_OUT::SIZE;
		bool ret = true;
		for (int i=0;i<size;i++)
		{
			if (adp[i]!=pRef.m_data[i])
			{
				ret = false;
				break;
			}
		}
		return ret;
	}
};

template<typename T>
struct Test_mul : public T {

	using typename T::TSP_IN;
	using typename T::REF_IN;
	using typename T::TSP_IN_2;
	using typename T::REF_IN_2;

	typedef TSP_IN_2 TSP_OUT;
	typedef REF_IN_2 REF_OUT;

	std::string Error_String;
	std::string Pass_String;

	Test_mul():Error_String("MUL Test Fail"),Pass_String("MUL Test Pass"){}

	void run_test(TSP_IN& pT0, TSP_IN& pT1, TSP_OUT &pOT)
	{
		typename TSP_IN::const_ADP_TYP adp0(pT0);
		typename TSP_IN::const_ADP_TYP adp1(pT1);
		typename TSP_OUT::ADP_TYP adpT(pOT);

		LARGE_INT::OPERA_UINT32::opera_mul(adp0,adp1,adpT);
	}

	void run_ref(const REF_IN& pR0, const REF_IN& pR1, REF_OUT &pOT)
	{
		pOT = pR0 * pR1;
	}

	bool comp_result(TSP_OUT& pTst, const REF_OUT& pRef)
	{
		typename TSP_IN::const_ADP_TYP adp(pTst);
		const int size = TSP_OUT::SIZE;
		bool ret = true;
		for (int i=0;i<size;i++)
		{
			if (adp[i]!=pRef.m_data[i])
			{
				ret = false;
				break;
			}
		}
		return ret;
	}
};